/*******************************************************************************
 * Copyright (c) 2011, 2011 Xbeam Project and ChenZhiKong.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 *******************************************************************************/
package org.bellusoft.xbeam.framework.resolve;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bellusoft.xbeam.framework.resolve.ModuleResolveResult.ResolveState;

/**
 * @NotThreadSafe
 */
class ResolveProcess{
	
	private final ResolverImp resolver ; 
	
	private final LinkedHashMap<ModuleResolveData, ModuleResolveResult> resolvedResults ;
	
	private final List<ModuleResolvingState> resolvingStates ; 
	
	//candidate modules indexed by module name
	private final Map<String/*name*/,List<ModuleResolvingState>> moduleMap = new HashMap<String, List<ModuleResolvingState>>();
	//candidate export packages indexed by package name
	private final Map<String/*name*/,List<ExportPackage>> exportPackageMap = new HashMap<String, List<ExportPackage>>(); 
	
	private final List<ModuleResolvingState> failResolveds = new ArrayList<ModuleResolvingState>(1); 

	ResolveProcess(ResolverImp resolver, List<ModuleResolveData> modules) {
		this.resolver = resolver ; 
		this.resolvingStates = new ArrayList<ModuleResolvingState>(modules.size());
		this.resolvedResults = new LinkedHashMap<ModuleResolveData, ModuleResolveResult>(modules.size());
		for(ModuleResolveData rd : modules){
			ResolveState status = resolver.resolveContext.getModuleStatus(rd);
			if(status!=null){
				if(status == ResolveState.Resolved){
					resolvedResults.put(rd, ResolvedModuleResolveResult.instance());
				}else if(status == ResolveState.Confirming){
					resolvedResults.put(rd, ConfirmingModuleResolveResult.instance());
				}else{
					//No other condition.
				}
			}else{
				ModuleResolvingState resolvingState = new ModuleResolvingState(rd); 
				resolvingStates.add(resolvingState);
				resolvedResults.put(rd, resolvingState);
			}
		}
	}
	
	Map<ModuleResolveData,ModuleResolveResult> run() {
		//TODO: one module with Import package and export package with the same name. 
		indexCandidateCapabilityInformation(); 
		tryWireModules();
		//set final state of result.
		debugCyclicDependencies();
		return resolvedResults ; 
	}
	
	private void debugCyclicDependencies() {
		// TODO Auto-generated method stub
	}

	private void tryWireModules() {
		List<ModuleResolvingState> toBeResolveds = resolvingStates;
		List<ModuleResolvingState> tempResolveds ;  
		List<ModuleResolvingState> localFaileds = new ArrayList<ResolveProcess.ModuleResolvingState>(); 
		while(true){
			tempResolveds = new ArrayList<ResolveProcess.ModuleResolvingState>(); 
			for(ModuleResolvingState state : toBeResolveds){
				boolean successed = tryResolveModule(state); 
				if(!successed){
					localFaileds.add(state);
					removeWiresAndDependencies(state) ; 
				}else{
					tempResolveds.add(state);
				}
			}
			/*
			 *(1)If all modules in @{toBeResolveds} have been resolved, then there is no need to wire again.
			 *(2)If there are any modules in @{toBeResolveds} can not be resolved, every element in @{tempResolved} must be checked.
			 *Because it may wire to some modules resolved failed and its' some wires has been removed.   
			 */
			if(localFaileds.size()==0){ //(1)
				break ; 
			}else{ //(2)
				failResolveds.addAll(localFaileds);
				localFaileds.clear() ; 
				toBeResolveds = tempResolveds ; 
			}
		}
	}

	private void removeWiresAndDependencies(ModuleResolvingState state) {
		//(1) remove wires of @{state.data} to other modules and related other modules' dependencies.
		for(Entry<RequiredModule, ModuleResolveData> entry: state.moduleWires.entrySet()){
			if(resolvedResults.containsKey(entry.getValue())){
				ModuleResolvingState provider = (ModuleResolvingState)resolvedResults.get(entry.getValue()) ; 
				provider.removeModuleWireAsProvider(entry.getKey());
			}else{
				//@{entry.getValue()} not contained , it must already be wired by call @{tryWireModuleFromWiredPool()}.
			}
		}
		for(Entry<ImportPackage,ExportPackage> entry : state.packageWires.entrySet()){
			if(resolvedResults.containsKey(entry.getValue().getModule())){
				ModuleResolvingState provider = (ModuleResolvingState)resolvedResults.get(entry.getValue().getModule()) ; 
				provider.removePackageWireAsProvider(entry.getKey(),entry.getValue());
			}else{
				//@{entry.getValue()} not contained , it must already be wired by call @{tryWirePackageFromWiredPool()}.
			}
		}
		
		//(2.1) remove dependencies to @{state.data} and related other modules' wires ;
		//(2.2) remove index value from @{exportPackageMap} and @{moduleMap} .
		for(RequiredModule m : state.moduleDependencies){
			ModuleResolvingState dependent = (ModuleResolvingState) resolvedResults.get(m.getModule());
			dependent.removeModuleWireAsDependent(m); 
		}
		state.moduleWires.clear();
		moduleMap.remove(state.data);
		
		for(Entry<ExportPackage,List<ImportPackage>> entry: state.packageDependencies.entrySet()){
			for(ImportPackage ip : entry.getValue()){
				ModuleResolvingState packageDependent = (ModuleResolvingState) resolvedResults.get(ip.getModule());
				packageDependent.removePackageWireAsDependent(ip);
			}
			exportPackageMap.get(entry.getKey().getName()).remove(entry.getKey());
		}
		state.packageWires.clear(); 
	}


	private boolean tryResolveModule(ModuleResolvingState state) {
		for(RequiredModule rm : state.getUnwiredRequiredModules()){
			ModuleResolveData wiredModule = tryWireModuleFromWiredPool(rm);
			if(wiredModule!=null){
				state.addModuleWireAsDependent(rm,wiredModule); 
			}else{
				wiredModule = tryWireModuleFromLocalProcess(rm); 
				if(wiredModule!=null){
					state.addModuleWireAsDependent(rm,wiredModule); 
					ModuleResolvingState provider = (ModuleResolvingState) resolvedResults.get(wiredModule);
					provider.addModuleWireAsProvider(rm);
				}
			}
			if(wiredModule==null){
				state.failedReason = "Dependency "+rm.value()+" can not be resolved."; 
				resolver.debug("Resolve Failed: "+state.data.value()+". Reason: "+state.failedReason);
				return false ; 
			}
		}
		for(ImportPackage ip : state.getUnwiredImportPackages()){
			ExportPackage exportPackage = tryWirePackageFromWiredPool(ip);
			if(exportPackage!=null){
				state.addPackageWireAsDependent(ip, exportPackage);
			}else{
				exportPackage = tryWirePackageFromLocalCache(ip);
				if(exportPackage!=null){
					state.addPackageWireAsDependent(ip, exportPackage);
					ModuleResolvingState provider = (ModuleResolvingState) resolvedResults.get(exportPackage.getModule());
					provider.addPackageWireAsProvider(ip, exportPackage);
				}
			}
			if(ip.isMandatory()){
				state.failedReason = "Dependency "+ip.value()+" can not be resolved."; 
				resolver.debug("Resolve Failed: "+state.data.value()+". Reason: "+state.failedReason);
				return false ; 
			}
		}
		return true ; 
	}

	private ExportPackage tryWirePackageFromLocalCache(ImportPackage ip) {
		List<ExportPackage> eps = exportPackageMap.get(ip.getName());
		for(ExportPackage ep : eps){
			if(resolver.packageMatcher.match(ep, ip)){
				return ep ; 
			}
		}
		return null;
	}

	private ExportPackage tryWirePackageFromWiredPool(ImportPackage ip) {
		return null;
	}

	private ModuleResolveData tryWireModuleFromLocalProcess(RequiredModule rm) {
		List<ModuleResolvingState> states = moduleMap.get(rm.getName());
		for(ModuleResolvingState state : states){
			if(resolver.moduleMatcher.match(state.data, rm)){
				return state.data ; 
			}
		}
		return null;
	}

	private ModuleResolveData tryWireModuleFromWiredPool(RequiredModule rm) {
		return null;
	}

	private void indexCandidateCapabilityInformation() {
		
		for(ModuleResolvingState mrs : resolvingStates){
			
			List<ModuleResolvingState> sameNameStates = moduleMap.get(mrs.data.getName());
			if(sameNameStates==null){
				sameNameStates = new ArrayList<ResolveProcess.ModuleResolvingState>(1);
				moduleMap.put(mrs.data.getName(), sameNameStates);
			}
			addAndSortModuleList(sameNameStates,mrs);
			
			for(ExportPackage ep : mrs.data.getExportPackages()){
				List<ExportPackage> sameNameExportPackages = exportPackageMap.get(ep.getName());
				if(sameNameExportPackages==null){
					sameNameExportPackages = new ArrayList<ExportPackage>(1);
					exportPackageMap.put(ep.getName(), sameNameExportPackages);
				}
				addAndSortExportPackageList(sameNameExportPackages, ep);
			}
			
		}
		
	}
	
	private void addAndSortModuleList(
			List<ModuleResolvingState> sameNameStates, ModuleResolvingState mrs) {
		for(int i=0; i<sameNameStates.size(); i++){
			if(resolver.modulePriorityComparator.compare(mrs.data, sameNameStates.get(i).data)>0){
				sameNameStates.add(i, mrs);
				return ; 
			}
		}
		//mrs's priority is lower than any ModuleResolvingState in sameNameStates.
		sameNameStates.add(mrs);
	}

	private void addAndSortExportPackageList(
			List<ExportPackage> sameNameExportPackages, ExportPackage ep) {
		for(int i=0; i<sameNameExportPackages.size(); i++){
			if(resolver.packagePriorityComparator.compare(ep, sameNameExportPackages.get(i))>0){
				sameNameExportPackages.add(i, ep);
				return ; 
			}
		}
		//ep's priority is lower than any ExportPackage in sameNameExportPackages.
		sameNameExportPackages.add(ep);
	}

	private final static Map<ImportPackage, ExportPackage> Empty_Package_Wires = Collections.emptyMap();
   
	private final static Map<RequiredModule, ModuleResolveData> Empty_Module_Wires = Collections.emptyMap();
	
	private final static Map<ExportPackage, List<ImportPackage>> Empty_Package_Dependencies = Collections.emptyMap();
	
	private final static List<RequiredModule> Empty_Module_Dependencies = Collections.emptyList();

	private class ModuleResolvingState implements ModuleResolveResult{
		
		private ResolveState state ; 
		
		private String failedReason ; 
		
		private ModuleResolveData data ; 
		
		private Map<ImportPackage, ExportPackage> packageWires = Empty_Package_Wires; 
		
		private Map<RequiredModule, ModuleResolveData> moduleWires = Empty_Module_Wires; 
		
		private Map<ExportPackage, List<ImportPackage>> packageDependencies = Empty_Package_Dependencies ; 
		
		private List<RequiredModule> moduleDependencies = Empty_Module_Dependencies; 
		
		private ModuleResolvingState(ModuleResolveData data) {
			this.data = data ; 
		}
		
		private void addModuleWireAsDependent(RequiredModule rm, 
				ModuleResolveData module) {
			moduleWires.put(rm, module);
		}
		
		private void addPackageWireAsDependent(ImportPackage ip, ExportPackage ep){
			packageWires.put(ip, ep);
		}
		
		private void removePackageWireAsDependent(ImportPackage ip) {
			packageWires.remove(ip);
		}

		private void removeModuleWireAsDependent(RequiredModule m) {
			moduleWires.remove(m);
		}

		private void addPackageWireAsProvider(ImportPackage importPackage,
				ExportPackage exportPackage) {
			List<ImportPackage> importPackages ; 
			if(packageDependencies == Empty_Package_Dependencies){
				packageDependencies = new HashMap<ExportPackage, List<ImportPackage>>(1);
				importPackages = new ArrayList<ImportPackage>(1) ; 
				packageDependencies.put(exportPackage,importPackages);
			}else{
				importPackages = packageDependencies.get(exportPackage);
			}
			importPackages.add(importPackage);
		}

		private void addModuleWireAsProvider(RequiredModule rm) {
			if(moduleDependencies==null){
				moduleDependencies = new ArrayList<RequiredModule>(1);
			}
			moduleDependencies.add(rm);
		}

		private void removePackageWireAsProvider(ImportPackage importPackage, ExportPackage exportPackage) {
			packageDependencies.get(exportPackage).remove(importPackage);
		}

		private void removeModuleWireAsProvider(RequiredModule requiredModule) {
			moduleDependencies.remove(requiredModule);
		}

		private List<RequiredModule> getUnwiredRequiredModules(){
			List<RequiredModule> rt = null ;
			for(RequiredModule rm : data.getRequireModules()){
				if(!moduleWires.containsKey(rm)){
					if(rt==null){
						rt = new ArrayList<RequiredModule>();
					}
					rt.add(rm);
				}
			}
			return rt ; 
		}
		
		private List<ImportPackage> getUnwiredImportPackages(){
			List<ImportPackage> rt = null ; 
			for(ImportPackage ip : data.getImportPackages()){
				if(!packageWires.containsKey(ip)){
					if(rt==null){
						rt = new ArrayList<ImportPackage>(1);
					}
					rt.add(ip);
				}
			}
			return rt ;
		}
		
		public Map<RequiredModule, ModuleResolveData> getModuleWires() {
			return moduleWires;
		}

		public Map<ImportPackage, ExportPackage> getPackageWires() {
			return packageWires;
		}

		public ResolveState getState() {
			return state;
		}

		public String getFailedReason() {
			return failedReason;
		}
		
	}

	
}
